﻿using System;
using System.Collections.Generic;
using System.Text;
using System.ComponentModel;
using System.IO;
using System.Windows.Media;
using System.Timers;
using VastPark.FrameworkBase.IO;

namespace ImmlPad.Documents
{
    public abstract class DocumentBase : IDocument, INotifyPropertyChanged
    {
        #region Events

        /// <summary>
        /// Fires when the document is detected as being modified by an external program
        /// </summary>
        public event EventHandler<EventArgs> DocumentModifiedExternally;

        protected virtual void OnDocumentModifiedExternally(EventArgs e)
        {
            EventHandler<EventArgs> handler = DocumentModifiedExternally;

            if (handler != null)
                handler(this, e);
        }

        /// <summary>
        /// Fires when the document is detected as being deleted by an external program
        /// </summary>
        public event EventHandler<EventArgs> DocumentDeletedExternally;

        protected virtual void OnDocumentDeletedExternally(EventArgs e)
        {
            EventHandler<EventArgs> handler = DocumentDeletedExternally;

            if (handler != null)
                handler(this, e);
        }

        #endregion

        public List<DocumentElement> Elements { get; set; }
        public bool EnableRenderedView { get; set; }

        private VastPark.FrameworkBase.IO.FileSystemWatcher _FileSystemWatcher;
        protected static string _TempFilenamePreface = "default";
        protected static int _TempFilenameCount = 0;
        
        /// <summary>
        /// Stores the initial text
        /// </summary>
        protected string _InitialText;
        private Timer _ElementUpdateTimer;

        private DocumentBase()
        {
            this.Elements = new List<DocumentElement>();
            
            _ElementUpdateTimer = new Timer(100);
            _ElementUpdateTimer.Elapsed += new ElapsedEventHandler(_ElementUpdateTimer_Elapsed);
        }

        private bool _IsUpdatingElements;

        void _ElementUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            if (_IsUpdatingElements)
                return;

            _IsUpdatingElements = true;
            _ElementUpdateTimer.Stop();

            _UpdateElements();
            _IsUpdatingElements = false;
        }


        public DocumentBase(string initialText)
            : this()
        {
            _Folder = Path.GetTempPath();
            _InitialText = initialText;
            _SourceText = _InitialText;            
            
            _FileSystemWatcher = new VastPark.FrameworkBase.IO.FileSystemWatcher();
            _FileSystemWatcher.FileSystemChanged += new EventHandler<FileSystemChangedArgs>(_FileSystemWatcher_FileSystemChanged);

            _UpdateElements();
        }

        public DocumentBase(string initialText, string fullPath, string foldingStates)
            : this()
        {
            _InitialText = initialText;
            _SourceText = _InitialText;

            _FileSystemWatcher = new VastPark.FrameworkBase.IO.FileSystemWatcher();
            _FileSystemWatcher.FileSystemChanged += new EventHandler<FileSystemChangedArgs>(_FileSystemWatcher_FileSystemChanged);
            
            this.FullPath = fullPath;
            this.FoldingStates = foldingStates;
            
            _UpdateElements();
        }

        /// <summary>
        /// Create object representations for all xml elements
        /// </summary>
        private void _UpdateElements()
        {
            this.Elements.Clear();
            
            try
            {
                System.Xml.XmlDocument xDoc = new System.Xml.XmlDocument();
                xDoc.Load(new MemoryStream(Encoding.ASCII.GetBytes(_SourceText)));

                _LoadNodes(xDoc.ChildNodes);
            }
            catch { }

            NotifyPropertyChanged("Elements");
        }

        private void _LoadNodes(System.Xml.XmlNodeList nodeList)
        {
            foreach (System.Xml.XmlNode element in nodeList)
            {
                if (element.HasChildNodes)
                {
                    _LoadNodes(element.ChildNodes);
                }

                if (element.Attributes != null && element.Attributes["Name"] != null)
                {
                    this.Elements.Add(new DocumentElement { Name = element.Attributes["Name"].Value, Type = element.Name });
                }
            }
        }

        void _FileSystemWatcher_FileSystemChanged(object sender, FileSystemChangedArgs e)
        {
            switch (e.Action)
            {
                case FileChangeType.Changed:
                    {
                        this.NeedsReload = true;
                        OnDocumentModifiedExternally(EventArgs.Empty);
                        break;
                    }
                case FileChangeType.Deleted:
                    {
                        OnDocumentDeletedExternally(EventArgs.Empty);
                        break;
                    }
            }
        }

        #region IDocument Members

        private string _Folder = "";
        public virtual string Folder
        {
            get
            {
                return _Folder;
            }
            private set
            {
                if (_Folder != value)
                {
                    _Folder = value;
                    NotifyPropertyChanged("Folder");
                    NotifyPropertyChanged("FullPath");
                }
            }
        }

        private string _Filename;
        public virtual string Filename
        {
            get
            {
                if (String.IsNullOrEmpty(_Filename))
                {
                    return TemporaryFilename;
                }
                else
                {
                    return _Filename;
                }
            }
            private set
            {
                if (_Filename != value)
                {
                    _Filename = value;                    
                    NotifyPropertyChanged("Filename");
                    NotifyPropertyChanged("FullPath");
                }
            }
        }

        string _TemporaryFilename = "";
        public virtual string TemporaryFilename
        {
            get
            {
                if (string.IsNullOrEmpty(_TemporaryFilename))
                {
                    string temp = "";
                    if (_TempFilenameCount == 0)
                    {
                        temp = _TempFilenamePreface + ".imml";
                    }
                    else
                    {
                        temp = _TempFilenamePreface + _TempFilenameCount + ".imml";
                    }
                    _TemporaryFilename = temp;
                    _TempFilenameCount++;
                }
                return _TemporaryFilename;
            }
        }


        private string _SourceText;
        public virtual string SourceText
        {
            get { return _SourceText; }
            set
            {
                if (_SourceText != value)
                {
                    _SourceText = value;

                    if (_SourceText != _InitialText)
                        this.NeedsSave = true;
                    else
                        this.NeedsSave = false;

                    _ElementUpdateTimer.Stop();
                    _ElementUpdateTimer.Start();

                    NotifyPropertyChanged("SourceText");
                }
            }
        }

        private bool _NeedsSave = false;
        public virtual bool NeedsSave
        {
            get { return _NeedsSave; }
            protected set
            {
                if (_NeedsSave != value)
                {
                    _NeedsSave = value;
                    NotifyPropertyChanged("NeedsSave");
                }
            }
        }

        private bool _NeedsReload;

        public virtual bool NeedsReload
        {
            get { return _NeedsReload; }
            set
            {
                if (_NeedsReload == value)
                    return;

                _NeedsReload = value;
                NotifyPropertyChanged("NeedsReload");                    
            }
        }

        public virtual bool UsingTemporaryFilename
        {
            get
            {
                return (String.IsNullOrEmpty(_Filename));
            }
        }
        
        private string _FoldingStates;

        public string FoldingStates
        {
            get { return _FoldingStates; }
            set
            {
                _FoldingStates = value;
            }
        }

        private string _FullPath;

        public virtual string FullPath
        {
            get
            {
                if (String.IsNullOrEmpty(this.Filename))
                {
                    return Path.Combine(this.Folder, this.TemporaryFilename);
                }
                else
                {
                    return Path.Combine(this.Folder, this.Filename);
                }
            }
            set
            {                
                this.Folder = Path.GetDirectoryName(value);
                this.Filename = Path.GetFileName(value);

                if(!_FullPath.IsNullOrEmpty())
                    _FileSystemWatcher.RemoveFile(_FullPath);

                _FileSystemWatcher.AddFile(value);
                _FileSystemWatcher.Enabled = true;
                _FullPath = value;
            }
        }

        public virtual string BackupPath
        {
            get
            {
                return Path.Combine(Path.GetDirectoryName(this.FullPath), Path.GetFileNameWithoutExtension(this.FullPath) + ".imml.bak");
            }
        }

        private ImageSource _PreviewImage;
        public virtual ImageSource PreviewImage
        {
            get
            {
                if (_PreviewImage == null)
                {
                    // look for a preview image on disk and load it
                    // Path.Combine(Path.GetDirectoryName(FullPath), Path.GetFileNameWithoutExtension(FullPath) + ".preview");
                }

                return _PreviewImage;
            }
            set
            {
                if (_PreviewImage != value)
                {
                    _PreviewImage = value;
                    NotifyPropertyChanged("PreviewImage");
                }
            }
        }

        public virtual void Reload()
        {
            _InitialText = System.IO.File.ReadAllText(this.FullPath);
            this.SourceText = _InitialText;
            this.NeedsReload = false;
        }

        public virtual bool SaveAs(string fullPath)
        {
            if (_SaveFile(fullPath))
            {
                NeedsSave = false;
                this.FullPath = fullPath;
                return true;
            }
            return false;
        }

        public virtual bool Save()
        {
            if (_SaveFile(FullPath))
            {
                NeedsSave = false;
                return true;
            }
            return false;
        }

        public virtual bool SaveBackup()
        {
            return _SaveFile(BackupPath);
        }

        public virtual void DeleteBackup()
        {
            if (File.Exists(this.BackupPath))
            {
                try
                {
                    File.Delete(this.BackupPath);
                }
                catch { }
            }
        }

        private bool _SaveFile(string fullPath)
        {
            try
            {
                _FileSystemWatcher.RemoveFile(fullPath);                
                File.WriteAllText(fullPath, SourceText);
                _FileSystemWatcher.AddFile(fullPath);
            }
            catch (Exception e)
            {
                return false;
            }

            return true;
        }

        #endregion

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion
    }
}
